#include "seat.h"
#include "util.h"
#include "keyboard.h"
#include "wlroots-full.hpp"
#include <wlr/backend.h>

static struct Input *
new_keyboard(struct Seat *seat, struct wlr_input_device *device, bool is_virtual)
{
#if 0
    struct wlr_keyboard *kb = wlr_keyboard_from_input_device(device);

    struct Keyboard *keyboard = ecalloc(1, sizeof(*keyboard));
    keyboard->base.wlr_input_device = device;
    keyboard->wlr_keyboard = kb;
    keyboard->is_virtual = is_virtual;

    wlr_keyboard_set_keymap(kb, seat->keyboard_group->keyboard.keymap);

    /*
     * This needs to be before wlr_keyboard_group_add_keyboard().
     * For some reason, wlroots takes the modifier state from the
     * new keyboard and syncs it to the others in the group, rather
     * than the other way around.
     */
    keyboard_set_numlock(kb);

    if (!is_virtual) {
        wlr_keyboard_group_add_keyboard(seat->keyboard_group, kb);
    }

    keyboard_setup_handlers(keyboard);

    wlr_seat_set_keyboard(seat->seat, kb);

    return (struct Input *)keyboard;
#endif
    return nullptr;
}

static void
input_device_destroy(struct wl_listener *listener, void *data)
{
    struct Input *input = wl_container_of(listener, input, destroy);
    wl_list_remove(&input->link);
    wl_list_remove(&input->destroy.link);

    /* `struct keyboard` is derived and has some extra clean up to do */
    if (input->wlr_input_device->type == WLR_INPUT_DEVICE_KEYBOARD) {
        struct Keyboard *keyboard = (struct Keyboard *)input;
        wl_list_remove(&keyboard->key.link);
        wl_list_remove(&keyboard->modifier.link);
        keyboard_cancel_keybind_repeat(keyboard);
    }
    free(input);
}

static void
seat_update_capabilities(struct Seat *seat)
{
    struct Input *input = NULL;
    uint32_t caps = 0;

    wl_list_for_each(input, &seat->inputs, link) {
        switch (input->wlr_input_device->type) {
        case WLR_INPUT_DEVICE_KEYBOARD:
            caps |= WL_SEAT_CAPABILITY_KEYBOARD;
            break;
        case WLR_INPUT_DEVICE_POINTER:
            caps |= WL_SEAT_CAPABILITY_POINTER;
            break;
        case WLR_INPUT_DEVICE_TOUCH:
            caps |= WL_SEAT_CAPABILITY_TOUCH;
            break;
        default:
            break;
        }
    }
    wlr_seat_set_capabilities(seat->seat, caps);
}

#if 0
static void
new_input_notify(struct wl_listener *listener, void *data)
{
    struct Seat *seat = wl_container_of(listener, seat, new_input);
    struct wlr_input_device *device = data;
    struct Input *input = NULL;

    switch (device->type) {
    case WLR_INPUT_DEVICE_KEYBOARD:
        input = new_keyboard(seat, device, false);
        break;
#if 0
    case WLR_INPUT_DEVICE_POINTER:
        input = new_pointer(seat, device);
        break;
    case WLR_INPUT_DEVICE_TOUCH:
        input = new_touch(seat, device);
        break;
    case WLR_INPUT_DEVICE_TABLET_TOOL:
        input = new_tablet(seat, device);
        break;
    case WLR_INPUT_DEVICE_TABLET_PAD:
        input = new_tablet_pad(seat, device);
        break;
#endif
    default:
        wlr_log(WLR_INFO, "unsupported input device");
        return;
    }

    input->seat = seat;
    input->destroy.notify = input_device_destroy;
    wl_signal_add(&input->wlr_input_device->events.destroy, &input->destroy);
    wl_list_insert(&seat->inputs, &input->link);

    seat_update_capabilities(seat);
}
#endif

void handle_new_input(wlr_input_device* device)
{
    switch (device->type) {
    case WLR_INPUT_DEVICE_KEYBOARD:
        input = new_keyboard(seat, device, false);
        break;
#if 0
    case WLR_INPUT_DEVICE_POINTER:
        input = new_pointer(seat, device);
        break;
    case WLR_INPUT_DEVICE_TOUCH:
        input = new_touch(seat, device);
        break;
    case WLR_INPUT_DEVICE_TABLET_TOOL:
        input = new_tablet(seat, device);
        break;
    case WLR_INPUT_DEVICE_TABLET_PAD:
        input = new_tablet_pad(seat, device);
        break;
#endif
    default:
        wlr_log(WLR_INFO, "unsupported input device");
        return;
    }

    input->seat = seat;
    input->destroy.notify = input_device_destroy;
    wl_signal_add(&input->wlr_input_device->events.destroy, &input->destroy);
    wl_list_insert(&seat->inputs, &input->link);

    seat_update_capabilities(seat);
}

#if 0
void seat_init(Server *server)
{
    server->seat = ecalloc(1, sizeof(Seat));
    server->seat->server = server;

    server->seat->seat = wlr_seat_create(server->dpy, "seat0");
    if (!server->seat->seat) {
        wlr_log(WLR_ERROR, "cannot allocate seat");
        exit(EXIT_FAILURE);
    }

    // wl_list_init(&server->seat->touch_points);
    // wl_list_init(&server->seat->constraint_commit.link);
    // wl_list_init(&server->seat->inputs);


    server->seat->new_input.notify = new_input_notify;
    wl_signal_add(&server->backend->events.new_input, &server->seat->new_input);

    // cursor_init(seat);
    keyboard_group_init(seat);
}
#endif

Seat::Seat(Server *server)
{
    m_server = server;

    m_seat = wlr_seat_create(server->wl_display(), "seat0");
    if (!m_seat) {
        wlr_log(WLR_ERROR, "cannot allocate seat");
        exit(EXIT_FAILURE);
    }

    // wl_list_init(&server->seat->touch_points);
    // wl_list_init(&server->seat->constraint_commit.link);
    // wl_list_init(&server->seat->inputs);


    m_new_input.connect(&server->wl_backend()->events.new_input, [&](void* data) {
        auto device = static_cast<wlr_input_device*>(data);
        struct Input *input = NULL;
        handle_new_input(device);

    });
    // wl_signal_add(&server->backend->events.new_input, &server->seat->new_input);

    // cursor_init(seat);
    keyboard_group_init(seat);
}
